﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Mesuro.Equation {

	public class EqOperatorDefinition {
		public static readonly uint PRECEDENCE_SYMBOL = 10;
		public static readonly uint PRECEDENCE_MULTIPLICATIVE = 5;
		public static readonly uint PRECEDENCE_ADDITIVE = 2;
		//--Static Definition --//
		public static readonly EqOperatorDefinition AssignmentOperatorDefinition;

		private static Dictionary<String, LinkedList<EqOperatorDefinition>> RegisteredOperators = new Dictionary<string, LinkedList<EqOperatorDefinition>>();

		public static EqOperatorDefinition Register( String Symbol, uint Precedence, EqOperatorArguments args, EqOperatorEvaluator eval ) {
			return Register( Symbol, Precedence, args, EqOperatorFlags.None, eval );
		}
		public static EqOperatorDefinition Register( String Symbol, uint Precedence, EqOperatorArguments args, EqOperatorFlags flags, EqOperatorEvaluator eval ) {
			LinkedList<EqOperatorDefinition> list;
			EqOperatorDefinition def;
			//Find the appropriate operator node
			if (!RegisteredOperators.TryGetValue( Symbol, out list )) {
				//Or create a new one
				RegisteredOperators.Add( Symbol, list = new LinkedList<EqOperatorDefinition>() );
				def = new EqOperatorDefinition( Symbol, (int)Precedence, args, flags, eval );
				list.AddFirst( def );
				return def;
			}

			//Get first operator in list
			LinkedListNode<EqOperatorDefinition> listNode = list.First;
			//Search for correct place for new operator
			while (listNode.Next != null && listNode.Value.Arguments > args)
				listNode = listNode.Next;

			//If an equivalent operator exists
			if (listNode.Value.Arguments == args) {
				//See if the definitions conflict
				if (listNode.Value.Precedence != Precedence) throw new InvalidOperationException( "Cannot define operator due precedence conflict with existing operators" );

				//Add a new evaluator to the list and return
				listNode.Value.AddEvaluator( eval );
				return listNode.Value;
			}

			//Create the operator definition
			def = new EqOperatorDefinition( Symbol, (int)Precedence, args, flags, eval );

			//Insert the new operator in the correct place
			if (listNode.Value.Arguments > args)//If we reached the end of the list before finding a higher precedence
				list.AddAfter( listNode, def );
			else
				list.AddBefore( listNode, def );

			return def;
		}

		public static LinkedList<EqOperatorDefinition> FindOperators( String symbol ) {
			LinkedList<EqOperatorDefinition> list;
			if (!RegisteredOperators.TryGetValue( symbol, out list ))
				return new LinkedList<EqOperatorDefinition>();
			return list;
		}
		public static EqOperatorDefinition FindOperator( String symbol, EqOperatorArguments arguments ) {

			LinkedList<EqOperatorDefinition> list;

			if (!RegisteredOperators.TryGetValue( symbol, out list ))
				return null;

			foreach (EqOperatorDefinition def in list)
				if (def.Arguments == arguments) return def;

			return null;
		}

		internal static bool AnyOperatorsStartWith( string p ) {
			foreach (String s in RegisteredOperators.Keys)
				if (s.StartsWith( p )) return true;
			return false;
		}

		static EqOperatorDefinition() {
			//Assignment
			AssignmentOperatorDefinition = Register( "=", 0, EqOperatorArguments.BINARY, EqOperatorFlags.HoldAll, delegate( EqScope scope, EqTerm LHS, EqTerm RHS, EqOperatorDefinition definition ) {
				EqSymbolInstance L;
				if ((L = LHS as EqSymbolInstance) != null) {
					scope.SetSymbol( L, RHS );
					return L.Evaluate( scope );
				} else return null;
			} );
			//List
			Register( ",", 1, EqOperatorArguments.BINARY, delegate( EqScope scope, EqTerm LHS, EqTerm RHS, EqOperatorDefinition definition ) {
				return EqListTerm.Combine( LHS, RHS );
			} );
		}

		//-- Instance Definition --//
		public readonly String Key;
		public readonly int Precedence;
		public readonly EqOperatorArguments Arguments;
		public readonly EqOperatorFlags Flags;

		public bool HoldLeft { get { return Flags.HasFlag( EqOperatorFlags.HoldLeft ); } }
		public bool HoldRight { get { return Flags.HasFlag( EqOperatorFlags.HoldRight ); } }

		private readonly LinkedList<EqOperatorEvaluator> Evaluators = new LinkedList<EqOperatorEvaluator>();

		private EqOperatorDefinition( String sym, int precedence, EqOperatorArguments args, EqOperatorFlags flags, EqOperatorEvaluator eval ) {
			this.Key = sym;
			this.Precedence = precedence;
			this.Arguments = args;
			this.Flags = flags;
			Evaluators.AddLast( eval );
		}

		private void AddEvaluator( EqOperatorEvaluator eval ) {
			Evaluators.AddLast( eval );
		}
		private void RemoveEvaluator( EqOperatorEvaluator eval ) {
			while (Evaluators.Remove( eval )) ;
		}

		public EqTerm Evaluate( EqScope scope, EqOperatorInstance instance ) {
			EqTerm result = null;
			LinkedListNode<EqOperatorEvaluator> eval = Evaluators.First;

			EqTerm L = (instance.LHS == null) ? null : HoldLeft ? instance.LHS : instance.LHS.Evaluate( scope );
			EqTerm R = (instance.RHS == null) ? null : HoldRight ? instance.RHS : instance.RHS.Evaluate( scope );

			while (result == null && eval != null) {
				result = eval.Value( scope, L, R, this );
				eval = eval.Next;
			}
			if (result == null)
				result = instance;
			return result;
		}

	}

	[Flags]
	public enum EqOperatorArguments { NONE = 0, RHS = 1, LHS = 2, SINGLE = NONE, BINARY = RHS | LHS, PREFIX = RHS, SUFFIX = LHS, POSTFIX = LHS };
	[Flags]
	public enum EqOperatorFlags {
		None = 0,
		//arg Evaluation
		HoldLeft = 1 << 0,
		HoldRight = 1 << 1,
		HoldAll = HoldLeft | HoldRight,
		//Algebraic mode
		Additive = 1 << 2,
		Multiplicative = 1 << 3,
		//Algebraic Properties
		Associative = 1 << 4,
		Commutative = 1 << 5,
		Identitive = 1 << 6,
		Distributive = 1 << 7,
		//Convenience Sets
		Addition = Additive | Associative | Commutative | Identitive | Distributive,
		Multiplication = Multiplicative | Associative | Commutative | Identitive | Distributive,
	};

	public delegate EqTerm EqOperatorEvaluator( EqScope Scope, EqTerm LHS, EqTerm RHS, EqOperatorDefinition definition );
}
